/*
 * Copyright (C) 2009 Rafael Fernandes, Jeff Sharkey, http://jsharkey.org/
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.phonebooksharing.android.services;

import java.util.LinkedList;
import java.util.Queue;

import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.os.IBinder;
import android.util.Log;
import android.widget.RemoteViews;

import com.phonebooksharing.android.provider.PhonebookSharing.Dashboard;
import com.phonebooksharing.android.util.CloseableUtil;
import com.phonebooksharing.android.util.NetworkHandlerTask;
import com.phonebooksharing.android.widgets.desktop.DashboardAppWidgetProvider;
import com.phonebooksharing.android.widgets.desktop.TextMessageAppWidgetProvider;
import com.phonebooksharing.android.widgets.desktop.VoiceMessageAppWidgetProvider;

/**
 * 
 * @author Jeff Sharkey, http://jsharkey.org/ (Examples)
 * @author Rafael Fernandes (Adapted based upon Sharkey's examples)
 *
 */
public class DesktopDashboardUpdaterService extends Service {
    private static final String TAG = "UpdateService";
    
    /**
     * Specific {@link Intent#setAction(String)} used when performing a full
     * update of all widgets, usually when an update alarm goes off.
     */
    public static final String ACTION_UPDATE_ALL = "com.phonebooksharing.android.DESKTOP_MESSAGE_WIDGETS_UPDATE";

    /**
     * Lock used when maintaining queue of requested updates.
     */
    private static Object sLock = new Object();

    /**
     * Flag if there is an update thread already running. We only launch a new
     * thread if one isn't already running.
     */
    private static boolean sThreadRunning = false;

    /**
     * Internal queue of requested widget updates. You <b>must</b> access
     * through {@link #requestUpdate(int[])} or {@link #getNextUpdate()} to make
     * sure your access is correctly synchronized.
     */
    private static Queue<Integer> sAppWidgetIds = new LinkedList<Integer>();

    /**
     * Request updates for the given widgets. Will only queue them up, you are
     * still responsible for starting a processing thread if needed, usually by
     * starting the parent service.
     */
    public static void requestUpdate(int[] appWidgetIds) {
        synchronized (sLock) {
            for (int appWidgetId : appWidgetIds) {
                sAppWidgetIds.add(appWidgetId);
            }
        }
    }

    /**
     * Peek if we have more updates to perform. This method is special because
     * it assumes you're calling from the update thread, and that you will
     * terminate if no updates remain. (It atomically resets
     * {@link #sThreadRunning} when none remain to prevent race conditions.)
     */
    private static boolean hasMoreUpdates() {
        synchronized (sLock) {
            boolean hasMore = !sAppWidgetIds.isEmpty();
            if (!hasMore) {
                sThreadRunning = false;
            }
            return hasMore;
        }
    }

    /**
     * Poll the next widget update in the queue.
     */
    private static int getNextUpdate() {
        synchronized (sLock) {
            if (sAppWidgetIds.peek() == null) {
                return AppWidgetManager.INVALID_APPWIDGET_ID;
            } else {
                return sAppWidgetIds.poll();
            }
        }
    }

    /**
     * Start this service, creating a background processing thread, if not
     * already running. If started with {@link #ACTION_UPDATE_ALL}, will
     * automatically add all widgets to the requested update queue.
     */
    @Override
    public void onStart(Intent intent, int startId) {
        super.onStart(intent, startId);

        // If requested, trigger update of all widgets
        if (ACTION_UPDATE_ALL.equals(intent.getAction())) {
            Log.d(TAG, "Requested UPDATE_ALL action");
            AppWidgetManager manager = AppWidgetManager.getInstance(this);
            requestUpdate(manager.getAppWidgetIds(new ComponentName(this, TextMessageAppWidgetProvider.class)));
            requestUpdate(manager.getAppWidgetIds(new ComponentName(this, VoiceMessageAppWidgetProvider.class)));
        }

        // Only start processing thread if not already running
        synchronized (sLock) {
            if (!sThreadRunning) {
                sThreadRunning = true;
                task = new DashDesktopUpdaterTask();
                task.execute(this);
            }
        }
    }
    
    private DashDesktopUpdaterTask task;
    
    private class DashDesktopUpdaterTask extends NetworkHandlerTask<Context, Void, Void> {
        @Override
        protected Void doInBackground(Context... params) {
            Context cx = params[0];
            AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(cx);
            ContentResolver cr = getContentResolver();
            
            while (hasMoreUpdates()) {
                int appWidgetId = getNextUpdate();
                
                Cursor c = null;
                try {
                    c = cr.query(Dashboard.CONTENT_URI, null, null, null, null);
                    Dashboard d = null;
                    if(c != null && c.moveToFirst()) {
                        d = Dashboard.fromCursor(c);
                    }
                    RemoteViews updateViews = DashboardAppWidgetProvider.buildUpdate(cx, d);
                    // Push this update to surface
                    if (updateViews != null) {
                        appWidgetManager.updateAppWidget(appWidgetId, updateViews);
                    }
                } finally {
                    CloseableUtil.safeClose(c);
                    c = null;
                }
            }
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            super.onPostExecute(result);
            // No updates remaining, so stop service
            stopSelf();
        }
    }
    
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}
